<?php if( !defined('IMP_VERSION') ) {


/*******************************************************************************
********************************************************************************
********************************************************************************
********************************************************************************
** Imp form automator
********************************************************************************
********************************************************************************
********************************************************************************
*******************************************************************************/




define('IMP_VERSION', '0.4.5b');




/*******************************************************************************
********************************************************************************
**
**


** Basics

Name:     Imp
Version:  (see define statement for IMP_VERSION)
Creators: Marcus Phillips - imp@marcusphillips.com
          Harry Pottash   - giblfiz@gmail.com
License:  MIT



** Hello!

Welcome to Imp.  We got the form thing covered.



** Purpose

Imp is a companion class to Smarty that facilitates form generation and validation.  It is meant to turn many dozens of lines of monotonous form related code into just a few, both in the form definition template and the logic on the back end.
It is designed chiefly around the idea of intuitive and accessible developer access, and every care is taken to strip the commands down to their most accessible and self-explanatory syntax.
A secondary design goal was a loose coupling between Imp the rest of the flow of PHP/HTML form submission.  As such, users should be able to access all of the standard form behavior they are used to, only picking the bits of Imp functionality that they need or want.



** Assumptions

- PHP 5 or higher (tested only on 5.2.5)
- You are using the Smarty templating engine
- Forms always submit back to the scripts that generated them
- Your goal is rapid form generation without sacrifising flexibility or customization.



** Limitations

- Imp has not been optimized for extremely high traffic.  Page load time is not noticably affected under light usage, but has not been tested at higher levels.
- Imp does not automate anything that would come at the cost of full flexibility and customization.  Thus, it presently has no auto styling of errors, or auto form layout.



** Capabilities

Currently, Imp has the following features in place for facilitating form processing:

= Form definition
  - Custom Smarty tags for automatic html generation
  - Compatible with plain html
= Form flow
  - Submission detection
  - Disambiguated multi form support (in testing)
  - automatic form instance management (in testing)
= Validation
  - Single-line validation constraints
  - Library of validation functions
  - Option for use of custom validation functions
  - Conditionally displayed form validation error messages
= Automatic field population
  - Presetting
  - User data repopulation
  - Resetting (repopulation override)
= Developer friendly
  - Configurable runtime behaviors (in testing)
  - Selectable optimizations (in testing)
  - Autodraw feature for further code reduction
  - Descriptive erroring for easy debug and code validity assurance
  - Powerful, flexible syntax for creative implementations
  - Intuitive, memorable syntax

Future goals for feature implementation include

= Automatic duplicated javascript validation
  - Ideally, individual fields would be submitted via AJAX in the background and compared using the same php functions as are run during final server-side validation
= Ordered processing validation constraints (and filters)
= Filters for user-submitted data that are applied before, after, and between validation checks


** Version history

0.4: 2007-01-26
Core functionality in place - begin testing

0.3: 2008-01-22
Second working waypoint in overhaul process

0.2: 2008-01-17
First major overhaul to create underlying structure necessary for the proposed feature set

0.1: 2007-12
Prototype proof of concept

0.0: 2007-09 (approximately)
Original conception by Marcus Phillips and Harry Pottash.




**
**
********************************************************************************
*******************************************************************************/








/*******************************************************************************
********************************************************************************
**
** Code comments and discussion


When the 'OPTIMISTIC' behavour is ENABLED, imp assumes that each form validation requirement is valid until told otherwise
In this case, you would use the invalidator() method to describe circumstances of invalidity.
The validator() method would create 'holes' in those circumstances where the data would still be considered valid
an example of this might be:

$imp->invalidator(   'broken_name', 'shorter_than', array(field('name'), 3)   ); // this means that the invalidator will find the requirement "broken_name" to be invalid if the function "shorter_than()" returns true when checking if the "name" is be shorter than 3
$imp->validator(     'broken_name', 'empty'       , array(field('name')   )   ); // this means that the validator will find the requirement "broken_name" to be valid if the function "empty()" returns true


in this case, the validators have presidence over the invalidators, and if any of them are found to validate the data, they override any invalidators that invalidated the data.  this also means that the validity for any untested requirement will default to 'valid' unless otherwise tampered with

thus, in optimist mode, the algorithm for validity of any given requirement can be said to be roughly:
$requirement_validity = $combined_relevant_validators OR  !$combined_relevant_invalidators;

With optimist behavior DISABLED, imp assumes that each form validation requirement is invalid until told otherwise
in pessimist mode, the algorithm for validity of any given requirement, in pseudo-code, is:
$requirement_validity = $combined_validators AND !$combined_invalidators;

regardless of the optimist setting, the validity for the form as a whole is always:
$form_validity = $combined_requirement_validities;


**
**
********************************************************************************
*******************************************************************************/


















/*******************************************************************************
********************************************************************************
********************************************************************************
********************************************************************************
** Head
********************************************************************************
********************************************************************************
********************************************************************************
*******************************************************************************/




// These definitions are used by the validators for determining what type of comparison shall be done to conclude validity
// See detailed remarks in the Imp_validator class
define('IMP_MATCH_BOOLEAN',    0);
define('IMP_NO_MATCH_BOOLEAN', 1);
define('IMP_MATCH_INEXACT',    2);
define('IMP_NO_MATCH_INEXACT', 3);
define('IMP_MATCH_EXACT',      4);
define('IMP_NO_MATCH_EXACT',   5);


// runtime settings
define('IMP_DEBUG', false);
define('IMP_INSTANCE_KEY_LENGTH', 5); // Imp uses a randomly generated instance key to distinguish various instantiations.  this setting determines the length of those strings


// default imp settings
define('IMP_DEFAULT_SMARTY_HANDLE', 'default');

define('IMP_DEFAULT_BEHAVIOR_PRESUMED_VALID'            , true             ); // IMPORTANT NOTE: changing this value will dramatically affect the behaviour of imp, making it incompatible with imp code written for the opposite setting.  We reccommend that you leave this setting to TRUE, and if you must alter it, do so with the configuration utility after instantiation
define('IMP_DEFAULT_BEHAVIOR_PASSWORDS_CLEAR_BY_DEFAULT', true             ); // Should imp_password tags have a reset value of '' by default
define('IMP_DEFAULT_REQUIRE_IMP_FORM_TAGS'              , true             ); // Should Imp allow users to create imp-related smarty tags outside of imp_form tags?  Use of Imp form tags allows enforcement of some important things and allows for graceful breakdown
define('IMP_DEFAULT_REQUIRE_IMP_ERROR_TAGS'             , false            ); // Should Imp require that each requirement mentioned in the script have a correlative error tag in the template
define('IMP_DEFAULT_REQUIRE_ERROR_REQUIREMENTS'         , true             ); // Should Imp require that each error listed in the template have a correlative requirement in the script?
define('IMP_DEFAULT_WARN_UNINDICITIVE_GET'              , false            ); // Show warning message any time data comes through the _GET and is not associated with this imp
define('IMP_DEFAULT_WARN_UNINDICITIVE_POST'             , false            ); // Same but for _POST
define('IMP_DEFAULT_WARN_UNINDICITIVE_FILES'            , false            ); // Same but for _FILES
define('IMP_DEFAULT_OPTIMIZE_SUBMITTED_CHECKING'        , true             ); // If this is set to true, Imp emembers the conclusion from the first run of submitted() and does not recalculate
define('IMP_DEFAULT_OPTIMIZE_VALIDATION'                , true             ); // Exits loop of evaluating validation functions (without executing any more than necessary) as soon as sufficient evidence is gathered
define('IMP_DEFAULT_MULTI_VALIDATION'                   , false            ); // Should Imp remember its validity state or recalculate every time validate() is called?



// Unstable features and options that are not fully tested

// Immediately after checking form validation, Imp can be set autodraw a template that is supplied at construction time
// The autodraw settings tell imp under what circumstances it should automatically render the tempate
define('IMP_AUTODRAW_NONE'   , 0);
define('IMP_AUTODRAW_INITIAL', 1);
define('IMP_AUTODRAW_INVALID', 2);
define('IMP_AUTODRAW_VALID'  , 4);
define('IMP_AUTODRAW_INITIAL_INVALID', IMP_AUTODRAW_INITIAL+IMP_AUTODRAW_INVALID);
define('IMP_AUTODRAW_INITIAL_VALID', IMP_AUTODRAW_INITIAL+IMP_AUTODRAW_VALID);
define('IMP_AUTODRAW_INVALID_VALID', IMP_AUTODRAW_INVALID+IMP_AUTODRAW_VALID);
define('IMP_AUTODRAW_INITIAL_INVALID_VALID', IMP_AUTODRAW_INITIAL+IMP_AUTODRAW_INVALID+IMP_AUTODRAW_VALID);

define('IMP_DEFAULT_BEHAVIOR_AUTODRAW'                  , IMP_AUTODRAW_NONE); // See autodraw explanation above
define('IMP_DEFAULT_ENFORCE_IMP_FILE_REQUIREMENTS'      , true             ); // this setting forces the programmer to place imp_file tags within imp_form tags, so that proper enctype may be enforced (among other things)
define('IMP_DEFAULT_OPTIMIZE_UPLOADS'                   , false            ); // (unstable) this setting allows the page visitor to upload documents once, and have the file persist on the server through redisplays, so they neednt re-upload simply due to unrelated errors elsewhere in the form
define('IMP_DEFAULT_TEMP_DIRECTORY'                     , null             ); // This setting determines where Imp's temporary uploaded files will be stored while waiting for the visitor to finish fixing their form errors













/*******************************************************************************
********************************************************************************
********************************************************************************
********************************************************************************
** BODY
********************************************************************************
********************************************************************************
********************************************************************************
*******************************************************************************/








////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Helper classes and functions
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////




/*
** checktime() mimicks the structure of the checkdate() function, but for time values
** $hour, $minute, and $second must be integers
** Returns a boolean indicating whether or not those integers represent a valid 24-hour clock time value
*/
if( ! function_exists('checktime') ){
  function checktime( $hour, $minute, $second='00' ){
    return(
      is_int($hour)   and 0 <= $hour   and $hour   < 24 and
      is_int($minute) and 0 <= $minute and $minute < 60 and
      is_int($second) and 0 <= $second and $second < 60
    );
  }
}


/*
** imp_field() generates a wrapper object of type Imp_field (explained below)
** string $field must be a string
** returns an Imp_field object pointing to the submission array index of $field
*/
function imp_field( $field ){
  return new Imp_field($field);
}



// evaluation helper classes


/*
** Imp_field is a helper class that acts as a wrapper for deferring and conditionally accessing submission values.
** By using Imp_fields to access values within the submission, Imp can pull the values from whatever method they arrive, get or post.
** Imp detects where the submission data is coming from, and translates the fields to their values only if and when they are actually needed
*/
class Imp_field { // Field is a helper class that allows for disambiguation of simple strings from names of user-submitted variables
  function __construct( $field ){
    if( !is_string($field) ){ trigger_error('Imp_field() constructor requires a string for parameter 1', E_USER_ERROR); }
    $this->field = $field;
  }
}

/*
** Imp_evaluators help to delay value calculation until the appropriate time for Imp
** Only useful internally to Imp
** They are used for (in)validators, tipoffs, presets, and resets - all of which are collections of information (usually a function handle and some arguments) that can be used to calculate a value later
*/
class Imp_evaluator {

  /*
  ** Constructor
  ** Imp    $imp                      is the associated object for which Imp_evaluator is doing its work
  ** string $value_or_function_handle can be a function handle to be called later, or if $arguments is left null, $value_or_functionhandle is considered to be the precalculated result.
  ** array  $arguments                is an array of arguments to be passed to the function handle in $value_or_function_handle.
  ** returns nothing
  */
  function __construct( $imp, $value_or_function_handle, $arguments=null ){
    $this->imp = $imp;
    if( $arguments === null ){
      $this->value = $value_or_function_handle;
    }else{
      $this->function_handle = $value_or_function_handle;
      $this->arguments = $arguments;
    }
  }

  /*
  ** evaluate_imp_field translates an Imp_field object into the appropriate value
  ** mixed   $imp_field_or_value is usually an Imp_field object to be translated into its appropriate value from the submitted form (if there is one), though it can also be a simple value that is simply returned
  ** boolean $multi_translate    indicates whether the imp_field shoud be returned from a method source to be determined by the Imp or whether an array of values for both methods should be returned.  This prevents mutual recursion with the tipoff evaluator, which can also accept Imp_fields
  ** returns the value in the submission with the key $imp_field_or_value->field
  */
  private function evaluate_imp_field( $imp_field_or_value, $multi_translate ){
    if( get_class($imp_field_or_value) !== 'Imp_field' ){
      if( $multi_translate ){ return array('get'=>$imp_field_or_value,'post'=>$imp_field_or_value); }
      else                  { return $imp_field_or_value;                                           }
    }
    if( $multi_translate ){ return $this->imp->submissions($imp_field_or_value->field); }
    else                  { return $this->imp->submission( $imp_field_or_value->field); }
  }

  function evaluate( $multi_translate_imp_fields=false ){
    if( isset($this->value) ){
      return $this->evaluate_imp_field($this->value, $multi_translate_imp_fields);
    }
    foreach( $this->arguments as $which_argument => &$each_argument ){
      $each_argument = $this->evaluate_imp_field($each_argument, $multi_translate_imp_fields);
    }
    if( $this->function_handle === 'isset' ){ trigger_error('The isset() function cannot be used in any of the imp variable functions.'); } // isset cannot be used in this way, because the variable will always be set by the time it reaches our context
    if( $this->function_handle === 'empty' ){ // empty() is not a php function, but a language construct - working around
      if( sizeof($this->arguments) !== 1 ){ trigger_error('The empty() language construct requires exactly 1 parameter.', E_USER_ERROR); }
      return empty($this->arguments[0]);
    }elseif( is_array($this->function_handle) or function_exists($this->function_handle) ){
      return call_user_func_array(                  $this->function_handle , $this->arguments);
    }elseif(  method_exists($this->imp, $this->function_handle) ){
      return call_user_func_array(array($this->imp, $this->function_handle), $this->arguments);
    }else{
      trigger_error('The function handle "'.$this->function_handle.'" is not present in the global or the imp namespace.', E_USER_ERROR);
    }
  }
}




// validation helper classes

/*
** Imp_validation_handler is a helper class to handle all evaluations necessary to determine validity of the form.
** It collects the status of all the validators and requirements and provides the appropriate conclusion
*/
class Imp_validation_handler {

  function __construct( $imp ){
    $this->imp = $imp;
    $this->validity = null;
    $this->tags = array();
  }

  function validator( $requirements, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN, $corroboration=true, $validates=true ){
    if( !is_array($arguments) and $arguments !== null ){ trigger_user_error('Arguments parameter of the (in)validator function must be null of type array.', E_USER_ERROR);  }
    if( !is_array($requirements) ){ $requirements = array($requirements); }
    $validator = new Imp_validator($this->imp, $value_or_function_handle, $arguments, $match_type, $corroboration, $validates);
    $this->validators[] = &$validator;
    foreach( $requirements as $which_requirement => $each_requirement ){
      if( $each_requirement === false ){ trigger_error('imp doesnt support unnamed requirements yet', E_USER_ERROR); }//todo:: $each_requirement = ''; or somehow get an unnamed requirement going
      if( ! isset($this->requirements[$each_requirement]) ){
        $this->requirements[$each_requirement] = new Imp_requirement($this->imp);
	$this->tags[$each_requirement] = false;
      }
      $this->requirements[$each_requirement]->depend($validator);
    }
  }

  function tag( $requirement ){
    if( !is_array($requirement) ){
      $requirement = array($requirement);
    }
    foreach( $requirement as $which_requirement => $each_requirement ){
      $this->tags[$each_requirement] = true;
    }
  }

  function untagged(){
    $untagged_requirements = false;
    foreach( $this->tags as $which_tag => $each_tag ){
      if( ! $each_tag ){
        $untagged_requirements[$which_tag] = &$this->requirements[$each_requirement];
      }
    }
    return $untagged_requirements;
  }

  function validate( $which_requirement=null ){
    if( $this->imp->submitted() === false ){
      return null;
    }
    if( $which_requirement !== null ){
      return $this->requirements[$which_requirement]->validate();
    }
    if( $this->imp->settings['multi_validation'] or $this->validity === null ){
      $this->validity = true;
      foreach( $this->requirements as $which_requirement => $each_requirement ){
        $this->validity &= $each_requirement->validate();
      }
    }
    return $this->validity;
  }
}


/*
** Imp_requirements are items that a form must recieve permission from before being considered valid
** One form may have many requirements, and one requiremnt may have many validators.  Many requirements may also depend on the same validator.
** To remember, imagine that each requirement is tied to a single error that you might display a user.  There could be several things that trigger that error, and there might be several errors that trigger in one page.
*/
class Imp_requirement {
  function __construct( $imp ){
    $this->imp = $imp;
    $this->validity = null;
  }
  function depend( &$validator ){
    $this->validators[] = &$validator;
  }
  function validate(){
    if( $this->imp->settings['multi_validation'] or $this->validity === null ){
      $validated   = false;
      $invalidated = false;
      foreach( $this->validators as $which_validator => &$each_validator ){
        if( $this->imp->settings['optimize_validation'] ){
          if( $this->imp->settings['behavior_presumed_valid'] and $validated or ! $this->imp->settings['behavior_presumed_valid'] and $invalidated ){ break   ; }
          if( $each_validator->validates       and $validated or ! $each_validator->validates       and $invalidated ){ continue; }
	}
        if( $each_validator->validates ){ $validated   ^= $each_validator->conclusion();
        }else{                            $invalidated ^= $each_validator->conclusion();
        }
      }
      if( $this->imp->settings['behavior_presumed_valid'] ){ $this->validity = ($validated or  ! $invalidated);
      }else{                                  $this->validity = ($validated and ! $invalidated);
      }
    }
    return $this->validity;
  }
}


/*
** Imp_validators are collections of function information that are stored until called upon to draw a single conclusion.
** when called upon, an Imp_validator returns true or false in order to support or refute the validity of one or more requirements
** see Imp_requirements for a discussion of heirarchy between forms, requirements, and validators
*/
class Imp_validator {
  function __construct( $imp, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN, $corroboration=true, $validates=true ){
    $this->imp = $imp;
    $this->evaluator = new Imp_evaluator($imp, $value_or_function_handle, $arguments);
    $this->match_type = $match_type;
    $this->corroboration = $corroboration;
    $this->validates = (boolean) $validates;
    $this->conclusion = null;
  }

  function conclusion(){
    if( $this->imp->settings['multi_validation'] or $this->conclusion === null ){
      $result = $this->evaluator->evaluate();
      if(     $this->match_type === IMP_MATCH_EXACT      ){ $this->conclusion = (          $result ===           $this->corroboration); }
      elseif( $this->match_type === IMP_NO_MATCH_EXACT   ){ $this->conclusion = (          $result !==           $this->corroboration); }
      elseif( $this->match_type === IMP_MATCH_INEXACT    ){ $this->conclusion = (          $result ==            $this->corroboration); }
      elseif( $this->match_type === IMP_NO_MATCH_INEXACT ){ $this->conclusion = (          $result !=            $this->corroboration); }
      elseif( $this->match_type === IMP_MATCH_BOOLEAN    ){ $this->conclusion = ((boolean) $result === (boolean) $this->corroboration); }
      elseif( $this->match_type === IMP_NO_MATCH_BOOLEAN ){ $this->conclusion = ((boolean) $result !== (boolean) $this->corroboration); }
      else{ trigger_error('Invalid directive supplied for (in)validator.', E_USER_ERROR); }
    }
    return $this->conclusion;
  }
}








////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Main class
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////


class Imp {

  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  //
  // declarations and construction


  private $memory; // This variable is where all optional persistant data are stored.  See constructor for initializations of specific keys.

  /*
  ** $smarty is the smarty object to render the page
  ** $imp_name is the handle by which imp_tags will reference to associate themselves with particular imps (optional if you only have one imp)
  ** $tipoff_function_handle is the name of a function (or an array containing a class name and a method name) that will determine if and where user submissions are coming in
  ** $tipoff_function_arguments is an array of arguments to be passed to the tipoff function
  ** returns nothing
  */
  function __construct( &$smarty, $smarty_handle=null, $tipoff_value_or_function_handle=null, $tipoff_arguments=null, $template=null ){

    if( ! get_class($smarty) === 'Smarty' ){ trigger_error('Imp requires an object of type "Smarty" for its first parameter', E_USER_ERROR); }
    if( $smarty_handle === IMP_DEFAULT_SMARTY_HANDLE ){ trigger_error('The smarty_handle "'.$smarty_handle.'" is reserved for the default imp.', E_USER_ERROR); }

    if( !isset($_SESSION) ){
      if( headers_sent() ){ trigger_error('Imp is trying to start a session, but cannot because the headers have already been sent.  Try running session_start() before any headers go out.', E_USER_WARNING); }
      session_start();
    }

    $this->settings['behavior_autodraw'                  ] = IMP_DEFAULT_BEHAVIOR_AUTODRAW                  ;
    $this->settings['behavior_presumed_valid'            ] = IMP_DEFAULT_BEHAVIOR_OPTIMISTIC                ;
    $this->settings['behavior_passwords_clear_by_default'] = IMP_DEFAULT_BEHAVIOR_PASSWORDS_CLEAR_BY_DEFAULT;
    $this->settings['require_imp_form_tags'              ] = IMP_DEFAULT_REQUIRE_IMP_FORM_TAGS              ;
    $this->settings['require_imp_error_tags'             ] = IMP_DEFAULT_REQUIRE_IMP_ERROR_TAGS             ;
    $this->settings['require_error_requirements'         ] = IMP_DEFAULT_REQUIRE_ERROR_REQUIREMENTS         ;
    $this->settings['warn_unindicitive_post'             ] = IMP_DEFAULT_WARN_UNINDICITIVE_POST             ;
    $this->settings['warn_unindicitive_get'              ] = IMP_DEFAULT_WARN_UNINDICITIVE_GET              ;
    $this->settings['warn_unindicitive_files'            ] = IMP_DEFAULT_WARN_UNINDICITIVE_FILES            ;
    $this->settings['optimize_submitted_checking'        ] = IMP_DEFAULT_OPTIMIZE_SUBMITTED_CHECKING        ;
    $this->settings['optimize_validation'                ] = IMP_DEFAULT_OPTIMIZE_VALIDATION                ;
    $this->settings['multi_validation'                   ] = IMP_DEFAULT_MULTI_VALIDATION                   ;
    $this->settings['optimize_uploads'                   ] = IMP_DEFAULT_OPTIMIZE_UPLOADS                   ;
    $this->settings['enforce_imp_file_requirements'      ] = IMP_DEFAULT_ENFORCE_IMP_FILE_REQUIREMENTS      ;
    $this->settings['temp_directory'                     ] = IMP_DEFAULT_TEMP_DIRECTORY                     ;

    if( $this->settings['optimize_uploads'] and $this->settings['temp_directory'] === null ){ trigger_error('Imp cannot optimize file uploading without specifying a valid temp directory.', E_USER_ERROR); }
    $this->setup_temp_directory();

    $this->validation_handler = new Imp_validation_handler($this);
    $this->fields       = array(); //todo: where is this used?
    $this->hides        = array();
    $this->tipoff       = null;
    if( $tipoff_value_or_function_handle !== null ){
      $this->tipoff($tipoff_value_or_function_handle, $tipoff_arguments);
    }elseif( $tipoff_arguments !== null ){
      trigger_error('You have supplied tipoff arguments but no tipoff function handle in your Imp constructor.', E_USER_ERROR);
    }
    $this->smarty_handle       = ( $smarty_handle === null ) ? IMP_DEFAULT_SMARTY_HANDLE : $smarty_handle;
    $this->template            = $template;
    $this->smarty              = &$smarty;
    $this->memory['submitted'] = null;
    $this->memory['instance' ] = null;
    $this->submitted();

    $this->setup_smarty();
  }

  /*
  ** setup_temp_directory() helps the constructor by checking on and handling the existance of this Imp's temporary directory
  ** accepts and returns nothing
  */
  private function setup_temp_directory(){
    if( $this->settings['temp_directory'] === null ){
      return;
    }
    if( ! file_exists($this->settings['temp_directory']) ){ trigger_error('The specified Imp temp directory, "'.$this->settings['temp_directory'].'" could not be found.', E_USER_ERROR); }
    //todo: find the right function to check permissions
    //if( fileperms($this->settings['temp_directory']) < 555 ){ trigger_error('The specified Imp temp directory , "'.$this->settings['temp_directory'].'" does not have the appropriate write permissions for writing by php.', E_USER_ERROR); }

    $success = true;
    $directories[] = 'uploads';
    foreach( $directories as $which_directory => $each_directory ){
      $directory = $this->settings['temp_directory'].$each_directory;
      if( ! file_exists($directory) ){
        $success = mkdir($directory);
        if( ! $success ){ trigger_error('The directory "'.$directory.'" could not be created.', E_USER_ERROR); }
      }
    }
  }


  /*
  ** setup_smarty() assists the constructor in registering the appropriate variables and functions to the smarty object
  ** it accepts and returns nothing
  */
  // NOTE: below is where many directives for the functionality of imp tags wihin smarty are defined
  private function setup_smarty(){
    $template_variables = &$this->smarty->get_template_vars();
    if( isset($template_variables['imps'][$this->smarty_handle]) ){ trigger_error('An imp by the smarty_handle "'.$this->smarty_handle.'" already exists within this Smarty.', E_USER_ERROR); }

    $template_variables[$this->smarty_handle] = &$this;
    $template_variables['imps'][$this->smarty_handle] = &$this;
    if( isset($template_variables['imps'][IMP_DEFAULT_SMARTY_HANDLE]) ){
      $template_variables['imp'] = &$template_variables['imps'][IMP_DEFAULT_SMARTY_HANDLE];
    }elseif( sizeof($template_variables['imps']) === 1 ){
      $template_variables['imp'] = &$this;
    }else{
      unset($template_variables['imp']); //NOTE: this means that introducing a second smarty_handled imp will remove the first one from the default position
    }

    if( isset($template_variables['imp_functions']) ){ return; }


    $template_variables['imp_tags']                     = array();
    $template_variables['imp_tags']['stack'  ]          = array();
    $template_variables['imp_tags']['tree'   ]          = array();
    $template_variables['imp_tags']['containing_child'] = null;


    $standard_block                                              = array();
    $standard_block[         'block']                            = true;
    $standard_block[         'parameters']                       = array();
    $standard_function                                           = $standard_block;
    $standard_function[      'block']                            = false;

    $standard_input_block                                        = $standard_block;
    $standard_input_block[   'parameters']['name' ]['allowance'] = 'required'  ;
    $standard_input_block[   'parameters']['type' ]['allowance'] = 'prohibited';
    $standard_input_block[   'parameters']['name' ]['attribute'] = true;
    $standard_input_block[   'parameters']['value']['allowance'] = 'prohibited';
    $standard_input_function                                     = $standard_input_block;
    $standard_input_function['block']                            = false;


    $functions['imp_error'   ]                                               = $standard_block;
    $functions['imp_error'   ]['parameters']['requirement'    ]['allowance'] = 'required';

    $functions['imp_form'    ]                                               = $standard_block;
    $functions['imp_form'    ]['parameters']['imp'            ]['attribute'] = false;
    $functions['imp_form'    ]['parameters']['method'         ]['default'  ] = 'post';
    $functions['imp_form'    ]['parameters']['method'         ]['attribute'] = true;
    $functions['imp_form'    ]['parameters']['action'         ]['default'  ] = '';
    $functions['imp_form'    ]['parameters']['action'         ]['attribute'] = true;

    $functions['imp_hidden'  ]                                               = $standard_input_function;
    $functions['imp_hidden'  ]['parameters']['preset'         ]['attribute'] = false;

    $functions['imp_text'    ]                                               = $standard_input_function;
    $functions['imp_textarea']                                               = $standard_input_function;
    $functions['imp_password']                                               = $standard_input_function;

    $functions['imp_checkbox']                                               = $standard_input_function;
    $functions['imp_checkbox']['parameters']['checked'        ]['allowance'] = 'prohibited';
    $functions['imp_radio'   ]                                               = $standard_input_function;
    $functions['imp_radio'   ]['parameters']['value'          ]['allowance'] = 'required';
    $functions['imp_radio'   ]['parameters']['value'          ]['attribute'] = true;
    $functions['imp_radio'   ]['parameters']['checked'        ]['allowance'] = 'prohibited';

    $functions['imp_select'  ]                                               = $standard_input_block;
    $functions['imp_option'  ]                                               = $standard_input_block;
    $functions['imp_option'  ]['parameters']['name'           ]['allowance'] = 'prohibited';
    $functions['imp_option'  ]['parameters']['value'          ]['allowance'] = 'required';
    $functions['imp_option'  ]['parameters']['value'          ]['attribute'] = true;
    $functions['imp_dropdown']                                               = $standard_input_function;
    $functions['imp_dropdown']['parameters']['options'        ]['allowance'] = 'required';

    $functions['imp_datetime']                                               = $standard_input_function;
    $functions['imp_datetime']['parameters']['name'           ]['attribute'] = false;
    $functions['imp_datetime']['parameters']['format'         ]['default'  ] = 'Y-m-d H:i:s';
    $functions['imp_datetime']['parameters'][  'year_start'   ]['default'  ] = date('Y');
    $functions['imp_datetime']['parameters'][  'year_end'     ]['default'  ] = "+1";
    $functions['imp_datetime']['parameters'][  'year_interval']['default'  ] = 1;
    $functions['imp_datetime']['parameters'][ 'month_start'   ]['default'  ] = 1;
    $functions['imp_datetime']['parameters'][ 'month_end'     ]['default'  ] = 12;
    $functions['imp_datetime']['parameters'][ 'month_interval']['default'  ] = 1;
    $functions['imp_datetime']['parameters'][   'day_start'   ]['default'  ] = 1;
    $functions['imp_datetime']['parameters'][   'day_end'     ]['default'  ] = 31;
    $functions['imp_datetime']['parameters'][   'day_interval']['default'  ] = 1;
    $functions['imp_datetime']['parameters'][  'hour_start'   ]['default'  ] = 0;
    $functions['imp_datetime']['parameters'][  'hour_end'     ]['default'  ] = 23;
    $functions['imp_datetime']['parameters'][  'hour_interval']['default'  ] = 1;
    $functions['imp_datetime']['parameters']['minute_start'   ]['default'  ] = 0;
    $functions['imp_datetime']['parameters']['minute_end'     ]['default'  ] = 59;
    $functions['imp_datetime']['parameters']['minute_interval']['default'  ] = 5;
    $functions['imp_datetime']['parameters']['second_start'   ]['default'  ] = 0;
    $functions['imp_datetime']['parameters']['second_end'     ]['default'  ] = 59;
    $functions['imp_datetime']['parameters']['second_interval']['default'  ] = 5;
    $functions['imp_date'    ]                                               = $functions['imp_datetime'];
    $functions['imp_date'    ]['parameters'][ 'format'        ]['default'  ] = 'Y-m-d';
    $functions['imp_time'    ]                                               = $functions['imp_datetime'];
    $functions['imp_time'    ]['parameters'][ 'format'        ]['default'  ] = 'H:i:s';

    $functions['imp_file'   ]                                                = $standard_input_function;

    $functions['imp_image'  ]                                                = $standard_input_function;
    $functions['imp_submit' ]                                                = $standard_input_function;
    $functions['imp_submit' ]['parameters'][ 'label'          ]['default'  ] = 'Submit';
    $functions['imp_submit' ]['parameters'][ 'name'           ]['allowance'] = 'allowed';

    //todo: maybe add optgroup, button, and reset as possible imp tags?

    $template_variables['imp_functions'] = $functions;
    foreach( $functions as $which_function => $each_function ){
      if( $each_function['block'] ){
        $this->smarty->register_block(   $which_function, array($this, $which_function));
      }else{
        $this->smarty->register_function($which_function, array($this, $which_function));
      }
    }
  }


  // custom smarty tags
  // These functions are gateways that all point to a single tag processor, thereby working around Smarty's limitation of linking each one distinct tag to one distinct function (as no disambiguation is possible)

  function imp_error(    $parameters, $content, &$smarty, $repeat ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_form(     $parameters, $content, &$smarty, $repeat ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_hidden(   $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_text(     $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_textarea( $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_password( $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_checkbox( $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_radio(    $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_select(   $parameters, $content, &$smarty, $repeat ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_option(   $parameters, $content, &$smarty, $repeat ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_dropdown( $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_file(     $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_image(    $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_submit(   $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  function imp_datetime( $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_date(     $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }
  function imp_time(     $parameters,           &$smarty          ){ return Imp::imp_tag( $parameters, (isset($content)?$content:null),  $smarty, (isset($repeat)?$repeat:null), __FUNCTION__ ); }

  //
  //
  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////








  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  //
  // basic configuration methods


  // settings

  /*
  ** Configure provides access to Imp's settings
  ** string $option  is the name of a behavior to adjust
  ** mixed  $setting is the new value to be assigned
  ** returns nothing
  */
  function configure( $option, $setting ){
    if( ! isset($this->settings[$option]) ){ trigger_error('The imp configuration variable you have specified cannot be found.', E_USER_ERROR); }
    $this->settings[$option] = $setting;
  }

  /*
  ** invalidator($requirement_name, $invalidating_function_name, $invalidating_function_arguments, $result_matching_directive, $corroborative_result);
  ** string $requirement_name                is a handle that links many invalidators to a single form constraint group.  many invalidators can invalidate a single requirement.
  ** string $invalidating_function_name      is the name of the function to call during the validation process.  the global namespace will be searched first, followed by the Imp namespace.  function handles of the form array($class_name, $method_name) are also accepted
  ** array  $invalidating_function_arguments is the set of arguments to be passed to the invalidation function.  if omitted, the $invalidating_function_name is assumed to be a pre-calculated result, and is used for comparison instead of running any invalidation at all.
  ** mixed  $result_matching_directive       (default: IMP_MATCH_BOOLEAN) is a parameter that indicates the type of comparison that will be done between the invalidating function's result and the corroborative_result.
  **   options are
  **     IMP_MATCH_BOOLEAN (default): casts the result as a bool before comparing it to the corroborative_result
  **     IMP_MATCH_INEXACT          : compares the result to the corroborative_result using the == operator
  **     IMP_MATCH_EXACT            : compares the result to the corroborative_result using the === operator
  **     (+ 3 more options using the convention "IMP_NO_MATCH_*").  these simply return the opposite of their correlatives
  ** mixed  $corroborative_result            (default: true) is the result that gets compared to the invalidating function's result.  if the results match (as defined by the $result_matching_directive parameter), then the associated requirement is cast as invalid
  ** returns nothing - this function only adds an instruction to a list that will be run when Imp::validate() is run in order to calculate form validity
  */
  function validator(   $requirement_names, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN,   $corroboration=true,   $validates  =true ){
    $this->validation_handler->validator( $requirement_names, $value_or_function_handle, $arguments, $match_type, $corroboration, $validates );
  }


  /*
  ** invalidator() acts as a wrapper, and uses validator()'s structure to accomplish inverse goals
  ** In optimistic mode, invalidators support the invalidity of an otherwise valid seeming form (read "if")
  ** In pecimistic mode, invalidators are only useful for trumping validators (read "unless")
  ** Accepts identical parameters as validator(), except the last one which is the inverse
  ** Returns nothing
  */
  function invalidator( $requirement_names, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN,   $corroboration=true,   $invalidates=true ){
    $this->validator(   $requirement_names, $value_or_function_handle, $arguments,      $match_type,                     $corroboration,      ! $invalidates      );
  }



  // These function synonyms might be more intuitively named for some users.  Considering using them instead.
  /*
  function allow(       $requirement_names, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN,   $allowable_evaluation   =true        ){
    $this->validator(   $requirement_names, $value_or_function_handle, $arguments,      $match_type,                     $allowable_evaluation        , true  );
  }
  function overlook(    $requirement_names, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN,   $overlookable_evaluation=true        ){
    $this->validator(   $requirement_names, $value_or_function_handle, $arguments,      $match_type,                   ! $disallowable_evaluation     , true  );
  }
  function demand(      $requirement_names, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN,   $demanded_evaluation    =true        ){
    $this->validator(   $requirement_names, $value_or_function_handle, $arguments,      $match_type,                     $demanded_evaluation         , false );
  }
  function forbid(      $requirement_names, $value_or_function_handle, $arguments=null, $match_type=IMP_MATCH_BOOLEAN,   $forbidden_evaluation   =true        ){
    $this->validator(   $requirement_names, $value_or_function_handle, $arguments,      $match_type,                   ! $forbidden_evaluation        , false );
  }
  */




  // server-side data management

  /*
  ** note() keeps track of a piece of information about a given form instance
  ** mixed $key is the accessor
  ** mixed $value is the important data
  ** returns nothing
  */
  function note( $key, $value ){
    $_SESSION['imps'][$this->instance()][$key] = $value;
  }

  /*
  ** recall() returns what was stored using note(), but does so by reference to allow direct, persistant access to the data stored there
  ** miked $key is a string or in to index to
  ** returns the value at that key, or null if no such key exists
  */
  function &recall( $key ){
    if( isset($_SESSION['imps'][$this->instance()][$key]) ){
      return $_SESSION['imps'][$this->instance()][$key];
    }
    $null = null;
    return $null;
  }

  function note_cloak_type( $field_name, $tag_type ){
    $_SESSION['imps'][$this->instance()]['cloaks'][$field_name]['type'] = $tag_type;
  }

  function &recall_cloak_type( $field_name ){
    if( isset($_SESSION['imps'][$this->instance()]['cloaks'][$field_name]) ){
      return $_SESSION['imps'][$this->instance()]['cloaks'][$field_name]['type'];
    }
    $null = null;
    return $null;
  }


  // client-side data management

  function hide( $field, $value_or_function_handle, $arguments=null ){
    $this->hides[$field] = true;
    $this->preset($field, $value_or_function_handle, $arguments);
  }

  function preset( $field, $value_or_function_handle, $arguments=null ){
    if( isset($this->fields[$field]['preset']) ){ trigger_error('A preset has already been defined for the field "'.$field.'".', E_USER_ERROR ); }
    $this->fields[$field]['preset'] = new Imp_evaluator($this, $value_or_function_handle, $arguments);
  }

  function reset( $field, $value_or_function_handle, $arguments=null ){
    if( isset($this->fields[$field]['reset']) ){ trigger_error('A reset has already been defined for the field "'.$field.'".', E_USER_ERROR ); }
    $this->fields[$field][ 'reset'] = new Imp_evaluator($this, $value_or_function_handle, $arguments);
  }


  // form state management

  private function tipoff( $value_or_function_handle, $arguments=null ){
    $this->tipoff = new Imp_evaluator($this, $value_or_function_handle, $arguments);
  }

  //
  //
  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////








  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  //
  // primary user-facing methods

  function instance(){
    if( !$this->settings['require_imp_form_tags'] ){ trigger_error('Use of imp instance management is not allowed without requiring imp_form tags.', E_USER_ERROR); }
    if( $this->memory['instance'] === null ){
      if( $this->submitted() ){
        $instance = $this->submission('imp');
        if( ! $instance ){ trigger_error('The imp instance number for "'.$this->smarty_handle.'" is missing from the submission.', E_USER_ERROR); }
        if( ! isset($_SESSION['imps'][$instance]) ){ trigger_error('The imp instance "'.$instance.'" of "'.$this->smarty_handle.'" has been submitted, but is missing from the session.', E_USER_ERROR); }
      }elseif( isset($this->memory['instance']) ){
        $instance = $this->memory['instance'];
      }else{
        $instance = substr(md5(rand()), 0, IMP_INSTANCE_KEY_LENGTH);
        $this->memory['instance'] = $instance;
      }
    }
    return $this->memory['instance'];
  }

  function &submitted(){
    if( (!$this->settings['require_imp_form_tags']) and $this->tipoff === null ){ trigger_error('You must either define a tipoff function or configure Imp to require imp_form_tags.', E_USER_ERROR); }
    if( (!$this->settings['optimize_submitted_checking']) or $this->memory['submitted'] === null ){
      if( $this->tipoff === null ){
	$method = $this->default_tipoff();
      }else{
        $method = $this->tipoff->evaluate(true);
      }
      if( $method !== false and $method !== 'get' and $method !== 'post' ){ trigger_error('Invalid response from tipoff function - expecting "get", "post", or false.', E_USER_ERROR); }
      if( $method === false and count($_GET  ) and $this->settings['warn_unindicitive_get'  ] ){ trigger_error('There are data in _GET, but submission of "'  .$this->smarty_handle.'" was found to be false.  Please check your submission tipoff.', E_USER_WARNING); }
      if( $method === false and count($_POST ) and $this->settings['warn_unindicitive_post' ] ){ trigger_error('There are data in _POST, but submission of "' .$this->smarty_handle.'" was found to be false.  Please check your submission tipoff.', E_USER_WARNING); }
      if( $method === false and count($_FILES) and $this->settings['warn_unindicitive_files'] ){ trigger_error('There are data in _FILES, but submission of "'.$this->smarty_handle.'" was found to be false.  Please check your submission tipoff.', E_USER_WARNING); }

      $this->memory['submitted'] = $method;
    }
    return $this->memory['submitted'];
  }

  function &submissions( $field_name=null ){
    $sources = array('post'=>null, 'get'=>null);
    foreach( $sources as $which_source => &$each_source ){
      $each_source = &$this->submission($field_name, $which_source);
    }
    return $sources;
  }

  function &submission( $field_name=null, $source=null ){
    if( $source === null ){
      $source = $this->submitted(); // note: if the tipoff must check the submission, passing a specific source is necessary to avoid recursion
    }
    if( $source === false ){
      $null = null;
      return $null;
    }
    $context = $this->method_to_reference($source);
    if( $field_name === null ){
      return $context;
    }
    if( ! is_string($field_name) or ! strlen($field_name) ){ trigger_error('Imp::submission() requires a string or null for parameter 1', E_USER_ERROR); }

    if( $field_name === 'imp' ){ // NOTE: this line prevents mutual recursion with the instance() function, which calls submission() to establish submitted instance number, but is called by it below when checking for cloaks
      if( isset($context[$field_name]) ){
        return $context[$field_name];
      }else{
        $null = null;
        return $null;
      }
    }

    $cloak_type = $this->recall_cloak_type($field_name);

    if(      $cloak_type === 'imp_file' ){
      return $this->uncloaked_imp_file($field_name);
    }elseif( $cloak_type === 'imp_datetime' or $cloak_type === 'imp_date' or $cloak_type === 'imp_time' ){
      return $this->uncloaked_imp_datetime($cloak_type, $field_name);
    }

    return $this->field($context, $this->parse_html_array_string($field_name));
  }

  function validate(){
    return $this->validation_handler->validate();
  }

  function handle_form( $directives=null ){
    if( $directives === null ){
      $directives = $this->settings['behavior_autodraw'];
    }

    if( $this->validation_handler->validate() ){
      $do_render = $this->check_flag(IMP_AUTODRAW_VALID,   $directives);
    }elseif( $this->submitted() ){
      $do_render = $this->check_flag(IMP_AUTODRAW_INVALID, $directives);
    }else{
      $do_render = $this->check_flag(IMP_AUTODRAW_INITIAL, $directives);
    }
    if( $do_render ){
      if( ! $this->template ){ trigger_error('Imp::handle_form() cannot AUTO_DRAW without a template defined.', E_USER_ERROR); }
      $this->smarty->display($this->template);
    }
    return $this->validation_handler->validate();
  }

  //
  //
  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////








  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  //
  // widely useful functions


  // evaluation

  function not_empty(        $data ){ return ! empty(                $data); }
  function is_not_numeric(   $data ){ return ! is_numeric(           $data); }
  function invalid_datetime( $data ){ return ! $this->valid_datetime($data); }
  function invalid_date(     $data ){ return ! $this->valid_date(    $data); }
  function invalid_time(     $date ){ return ! $this->valid_time(    $data); }
  function invalid_email(    $data ){ return ! $this->valid_email(   $data); }

  function longer_than(  $value, $maximum_length ){ return $maximum_length < strlen($value); }
  function shorter_than( $value, $minimum_length ){ return strlen($value) < $minimum_length; }

  function valid_datetime( $datetime ){
    $datetime = explode(' ', $datetime);
    if( sizeof($datetime) !== 2 ){ return false; }
    return $this->valid_date($datetime[0]) and $this->valid_time($datetime[1]);
  }
  function valid_date($date){
    if( ! isset($date) or $date == '' or ! is_string($date) ){ return false; }

    $date = explode('-', $date);
    if( sizeof($date) !== 3 ){
      return false;
    }
    foreach( $date as $which_field => $each_field ){
      if( ! is_numeric($each_field) or $each_field == '' ){ return false; }
    }
    return (bool) checkdate($date[1], $date[2], $date[0]);
  }
  function valid_time( $time ){
    $time = explode(':', $time);
    if( !isset($time[2]) ){ $time[2] = '00'; }
    return checktime( $time[0], $time[1], $time[2]);
  }

  function valid_email( $email ){
    if( $this->on_windows() ){
      return (bool) preg_match("/^[A-z0-9\._-]+"."@" . "[A-z0-9][A-z0-9-]*". "(\.[A-z0-9_-]+)*"."\.([A-z]{2,6})$/", $email); // NOTE: windows builds do not currently (02/08) support the checkdnsrr command - reverting to preg_match
    }
    $email = explode('@', $email);
    if( sizeof($email) !== 2 ){
      return false;
    }
    return sizeof($email[0]) and checkdnsrr($email[1], 'MX');
  }



  // conformance

  function make_date_valid( $date ){
    $date = explode(' ', $date);
    $date = explode('-', $date[0]);
    $date[0] = date('Y-m-d', mktime(0,0,0,$date[0][1],$date[0][2],$date[0][0]));
    $date = implode(' ', $date);
  }



  // tipoffs

  function default_tipoff(){ //NOTE: this function only works correctly when the form is setup to submit an instance number named 'imp' that matches one held in the session
    $methods = array('post', 'get');
    foreach( $methods as $which_method => $each_method ){
      $context = $this->method_to_reference($each_method);
      if( isset($context['imp']) and isset($_SESSION['imps'][$context['imp']]) and $_SESSION['imps'][$context['imp']]['smarty_handle'] === $this->smarty_handle ){
	return $each_method;
      }
    }
    return false;
  }

  function count_files(){ //NOTE: this function will never return 'get', even if you have made the mistake of configuring your form to use method="get"
    if(count($_FILES)){
      return 'post';
    }else{
      return false;
    }
  }



  //
  //
  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////








  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  //
  // internal helper functions



  // uncloaking functions

  private function &uncloaked_imp_file( $field_name ){
    if( isset($_FILES[$field_name]) and $_FILES[$field_name]['name'] !== '' ){
      return $_FILES[$field_name];
    }elseif( isset($_SESSION['imps'][$this->instance()]['cloaks'][$field_name]['value']) ){
      return $_SESSION['imps'][$this->instance()]['cloaks'][$field_name]['value'];
      //todo - this will recurse when called by the tipoff (instance())
    }
    $null = null;
    return $null;
  }

  private function &uncloaked_imp_datetime( $cloak_type, $field_name ){ //todo: there's no reason this function should return a reference, it just makes a notice go away
    $value = $this->field($this->submission(), $this->parse_html_array_string($field_name));
    if( $value === null ){
      return $value;
    }
    //todo - this will recurse when called by the tipoff (submission())
    if( $cloak_type === 'imp_datetime' or $cloak_type === 'imp_time' ){
      if( isset($value['meridiem']) and strtolower($value['meridiem']) === 'pm' and isset($value['hour']) ){
        if( $value['hour'] < 12 ){
          $value['hour'] += 12;
        }else{
          trigger_error('Meridiem selection coupled with 24hour hour selection.', E_USER_WARNING);
        }
      }
    }
    if( $cloak_type === 'imp_datetime' or $cloak_type === 'imp_date' or $cloak_type === 'imp_time' ){
      $year   = isset($value['year'  ]) ? $value['year'  ] : 'NOT_SUBMITTED';
      $month  = isset($value['month' ]) ? $value['month' ] : 'NOT_SUBMITTED';
      $day    = isset($value['day'   ]) ? $value['day'   ] : 'NOT_SUBMITTED';
      $hour   = isset($value['hour'  ]) ? $value['hour'  ] : '00';
      $minute = isset($value['minute']) ? $value['minute'] : '00';
      $second = isset($value['second']) ? $value['second'] : '00';
      $date = $year.'-'.$month.'-'.$day;
      $time = $hour.':'.$minute.':'.$second;
      $datetime = $date.' '.$time;
      if( $cloak_type === 'imp_datetime' ){
        $value = $datetime;
      }
      if( $cloak_type === 'imp_date' ){
        $value = $date;
      }
      if( $cloak_type === 'imp_time' ){
        $value = $time;
      }
    }
    return $value;
  }



  // basic data management and manipulation

  private function &field( &$context, $field_array ){
    foreach( $field_array as $which_index => $each_index ){
      if( ! strlen($each_index) ){ trigger_error('For validation purposes, Imp requires all fields to have explicit names, and forbids the indexing of the format "my_array[]".', E_USER_ERROR); }
      if( ! isset($context[$each_index]) ){
	$null = null;
        return $null;
      }
      $context = &$context[$each_index];
    }
    return $context;
  }

  private function check_flag( $flag, $flags ){
    $flag  = intval($flag );
    $flags = intval($flags);
    return (($flags and $flag) == $flag);
  }

  function on_windows(){
    return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
  }

  private function parse_path_string( $path_string ){
    return explode(DIRECTORY_SEPARATOR, $path_string);
  }

  private function parse_html_array_string( $array_string ){
    $atoms = explode('[', $array_string);
    foreach( $atoms as $which_atom => $each_atom ){
      if(      $which_atom === 0                ){
        $missing_closing_bracket = false                    ;
        $indices[$which_atom] = $each_atom;
      }else{
        $missing_closing_bracket = (substr($each_atom, -1) !== ']');
        $each_atom = substr($each_atom, 0, -1);
  	$indices[$which_atom] = $each_atom;
      }
      $extra_closing_brackets = (sizeof(explode(']', $each_atom)) !== 1);
      if( $missing_closing_bracket or $extra_closing_brackets ){ trigger_error('Poorly formatted array string.  Please use the format "my_array[first_index][second_index][etc..]" with no quotation marks.', E_USER_ERROR); }
    }
    return $indices;
  }

  private function &method_to_reference( $method ){
    $method = '_'.strtoupper( $method );
    global ${$method}; //NOTE: for some reason, this makes superglobals accessable as variable variables within a function
    if( !isset($$method) ){ trigger_error('The superglobal variable "'.$method.'" could not be found by Imp::method_to_reference().', E_USER_ERROR); }
    return $$method;
  }

  private function create_path( $path ){
    $context = '';
    foreach( $path as $which_directory => $each_directory ){
      $context .= $each_directory.'/';
      if( !file_exists($context) ) {
	$success = mkdir($context);
        if( !$success ){ trigger_error('Imp could not create the directory "'.$context.'".', E_USER_ERROR); }
      }
    }
  }

  //
  //
  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////








  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  //
  // functions to be called by Smarty

  // helper integration functions for smarty

  function display_value( $field_name ){
    if( $this->submitted() ){
      if( isset($this->fields[$field_name]['reset']) ){
        return $this->fields[$field_name]['reset']->evaluate();
      }else{
        if( $this->recall_cloak_type($field_name) === 'imp_file'){
          return null;
	}else{
          return $this->submission($field_name);
        }
      }
    }elseif( isset($this->fields[$field_name]['preset']) ){
      return $this->fields[$field_name]['preset']->evaluate();
    }else{
      return null;
    }
  }

  function &ancestor( &$smarty, $tag_type ){
    $template_variables = &$smarty->get_template_vars();
    $focal_ancestor = &$template_variables['imp_tags']['containing_child'];
    while( $focal_ancestor !== null ){
      if( $focal_ancestor['type'] === $tag_type ){
        return $focal_ancestor;
      }else{
	$focal_ancestor = &$focal_ancestor['parent'];
      }
    }
    return false;
  }

  function preserve_upload( $source_file_name, $field_name ){
    $path = $this->parse_path_string($this->settings['temp_directory'].'/uploads/'.$this->instance().'/');
    $this->create_path($path);
    copy($source_file_name, $this->settings['temp_directory'].'/uploads/'.$this->instance().'/'.$field_name);
  }


  // html generation functions

  function html_generic_tag( $name, $attributes=null, $block_type='neither', $content=null, $add_line_breaks=false ){
    if( $block_type !== 'open' and $block_type !== 'close' and $block_type !== 'both' and $block_type !== 'neither' ){ trigger_error('improper opening directive in parameter three of '.__FUNCTION__.'.', E_USER_ERROR); }
    if( $block_type !== 'both' and $content !== null ){ trigger_error('Content is not allowed for html_generic_tags other than block open+close (with third parameter of "both").', E_USER_ERROR); }
    if( $attributes === null ){
      $attributes = array();
    }
    $attribute_strings = '';
    foreach ($attributes as $which_attribute => $each_attribute ){
      $attribute_string .= ' '.$which_attribute.'="'.$each_attribute.'"';
    }
    return '<'.($block_type==='open'?'/':'').$name.($bock_type!=='close'?$attribute_string:'').($block_type==='neither'?'/':'').'>'.($block_type==='both'?($add_line_breaks?"\r":'').$content.($add_line_breaks?"\r":'').'</'.$name.'>':null);
  }


  function html_generic_input( $type, $name, $value=null, $attributes=null ){
    $attributes = is_array($attributes) ? $attributes : array() ;
    $primary_attributes = array('type' => $type, 'name' => $name, 'value' => $value);
    $attributes = array_merge( $primary_attributes, $attributes );
    return $this->html_generic_tag('input', $attributes );
  }


  function html_option(  $value, $content, $selection, $attributes=array() ){
    $attributes['value'] = $value;
    if( $selection == $value ){
      $attributes = array_merge($attributes, array('selected' => 'selected'));
    }
    return $this->html_generic_tag('option', $attributes, 'both', $content );
  }

  function html_options( $options, $selection, $global_attributes=array(), $individual_attributes=array() ){
    $options_string = '';
    foreach( $options as $which_option => $each_option ){
      (!isset($individual_attributes[$which_option])) ? $individual_attributes[$which_option] = array() : null;
      $attributes = array();
      $attributes = array_merge($attributes, array('value' => $which_option)      );
      $attributes = array_merge($attributes, $global_attributes                   );
      $attributes = array_merge($attributes, $individual_attributes[$which_option]);
      $options_string .= "\r".$this->html_option($which_option, $each_option, $selection, $attributes);
    }
    return $options_string."\r";
  }

  function html_datetime_options( $format, $selection, $interval, $start, $end, $attributes){
    $args = func_get_args();
    if( $format === 'h' or $format === 'g' ){
      if( 12 <= $start ){
        $start = $start - 12;
      }
      if( 12 <= $end   ){
        $end = 11;
      }
    }
    if( !ctype_digit((string)$start) ){
      $start = date($format) + (int) $start;
    }
    if( !ctype_digit((string)$end  ) ){
      $end   = (int) $start  + (int) $end  ;
    }

    $options = array();
    for( $which_option = (int) $start; $which_option <= $end; $which_option += $interval ){
      if(      $format === 'Y' or
               $format === 'y' or
               $format === 'o' ){
        $output                 = date($format, mktime(0,0,0,1,1,$which_option));
        $formatted_which_option = date('Y'    , mktime(0,0,0,1,1,$which_option));
      }elseif( $format === 'm' or
               $format === 'M' or
               $format === 'F' or
               $format === 'n'){
        $output                 = date($format, mktime(0,0,0,    $which_option));
        $formatted_which_option = date('m'    , mktime(0,0,0,    $which_option));
      }elseif( $format === 'd' or
               $format === 'D' or
               $format === 'j' ){
        $output                 = date($format, mktime(0,0,0,1,  $which_option));
        $formatted_which_option = date('d'    , mktime(0,0,0,1,  $which_option));
      }elseif( $format === 'H' or
               $format === 'h' or
               $format === 'G' or
               $format === 'g' ){
        $output                 = date($format, mktime(          $which_option));
        $formatted_which_option = date('H'    , mktime(          $which_option));
      }elseif( $format === 'i' ){
        $output                 = date($format, mktime(0,        $which_option));
        $formatted_which_option = date('i'    , mktime(0,        $which_option));
      }elseif( $format === 's' ){
        $output                 = date($format, mktime(0,0,      $which_option));
        $formatted_which_option = date('s'    , mktime(0,0,      $which_option));
      }elseif( $format === 'a' or
               $format === 'A' ){
        $output                 = date($format, mktime($which_option*12       ));
        $formatted_which_option = date('a'    , mktime($which_option*12       ));
      }
      $options[$formatted_which_option] = $output;
    }
    return $this->html_options($options, (int) $selection, $attributes);
  }

  function html_datetime_inputs( $field_name, $date, $time, $format, $constraints ){
    $date = $date === null ? null : explode('-', $date);
    $time = $time === null ? null : explode(':', $time);
    $defaults['year'    ]['interval'] = 1        ;
    $defaults['year'    ]['start'   ] = date('Y');
    $defaults['year'    ]['end'     ] = '+1'     ;
    $defaults['month'   ]['interval'] = 1        ;
    $defaults['month'   ]['start'   ] = 1        ;
    $defaults['month'   ]['end'     ] = 12       ;
    $defaults['day'     ]['interval'] = 1        ;
    $defaults['day'     ]['start'   ] = 1        ;
    $defaults['day'     ]['end'     ] = 31       ;
    $defaults['hour'    ]['interval'] = 1        ;
    $defaults['hour'    ]['start'   ] = 0        ;
    $defaults['hour'    ]['end'     ] = 23       ;
    $defaults['minute'  ]['interval'] = 5        ;
    $defaults['minute'  ]['start'   ] = 0        ;
    $defaults['minute'  ]['end'     ] = 59       ;
    $defaults['second'  ]['interval'] = 5        ;
    $defaults['second'  ]['start'   ] = 0        ;
    $defaults['second'  ]['end'     ] = 59       ;
    $defaults['meridiem']['interval'] = 1        ;
    $defaults['meridiem']['start'   ] = 0        ;
    $defaults['meridiem']['end'     ] = 1        ;

    $html = '';
    $options['year  '] = false;
    $options['month '] = false;
    $options['day   '] = false;
    $options['hour  '] = false;
    $options['minute'] = false;
    $options['second'] = false;
    $skip_character = false;
    $remaining_format = $format;
    while( strlen($remaining_format) ){
      $each_character = substr($remaining_format, 0, 1);
      $remaining_format = substr($remaining_format, 1);
      if(      $skip_character === true ){
        $html .= $each_character;
        $skip_character = false;
      }elseif( $each_character === 'Y' or
               $each_character === 'y' or
               $each_character === 'o' ){
        if( $date === null ){ trigger_error('Invalid format character "'.$each_character.'".', E_USER_ERROR); }
        $timescale = 'year';
        $selection = $date[0];
      }elseif( $each_character === 'm' or
               $each_character === 'M' or
               $each_character === 'F' or
               $each_character === 'n' ){
        if( $date === null ){ trigger_error('Invalid format character "'.$each_character.'".', E_USER_ERROR); }
        $timescale = 'month';
        $selection = $date[1];
      }elseif( $each_character === 'd' or
               $each_character === 'D' or
               $each_character === 'j' ){
        if( $date === null ){ trigger_error('Invalid format character "'.$each_character.'".', E_USER_ERROR); }
        $timescale = 'day';
        $selection = $date[2];
      }elseif( $each_character === 'H' or
               $each_character === 'h' or
               $each_character === 'G' or
               $each_character === 'g' ){
        if( $time === null ){ trigger_error('Invalid format character "'.$each_character.'".', E_USER_ERROR); }
        $timescale = 'hour';
        $selection = $time[0];
        if( $each_character === 'G' or $each_character === 'g' ){
          $selection = $selection % 12;
          $meridiem_requirement =  1;
        }else{
          $meridiem_requirement = -1;
	}
      }elseif( $each_character === 'i' ){
        if( $time === null ){ trigger_error('Invalid format character "'.$each_character.'".', E_USER_ERROR); }
        $timescale = 'minute';
        $selection = $time[1];
      }elseif( $each_character === 's' ){
        if( $time === null ){ trigger_error('Invalid format character "'.$each_character.'".', E_USER_ERROR); }
        $timescale = 'second';
        $selection = $time[2];
      }elseif( $each_character === 'a' or
               $each_character === 'A' ){
        if( $time === null ){ trigger_error('Invalid format character "'.$each_character.'".', E_USER_ERROR); }
        $timescale = 'meridiem';
        $selection = ($time[0] >= 12 ? 0 : 1);
      }elseif( $each_character === '\\' ){
        $skip_character = true;
        continue;
      }elseif( preg_match("[a-z]", $each_character) ){
        trigger_error('Sorry, imp_datetime does not support the formatting character "'.$each_character.'".  Try escaping it with "\" if you would like to see it in the output.', E_USER_ERROR);
      }else{
        $html .= $each_character;
        continue;
      }

      if( $options[$timescale] === true ){ trigger_error('The format parameter for the '.$tag_type.' tag contains duplicate selections for '.$timescale.'.', E_USER_ERROR); }
      $options[$timescale] = true;
      $interval = isset($constraints[$timescale.'_interval']) ? $constraints[$timescale.'_interval'] : $defaults[$timescale]['interval'];
      $start    = isset($constraints[$timescale.'_start'   ]) ? $constraints[$timescale.'_start'   ] : $defaults[$timescale]['start'   ];
      $end      = isset($constraints[$timescale.'_end'     ]) ? $constraints[$timescale.'_end'     ] : $defaults[$timescale]['end'     ];
      $attributes['name'] = $field_name.'['.$timescale.']"';
      $option_attributes = array();
      $html .= $this->html_generic_tag('select', $attributes, 'both', $this->html_datetime_options($each_character, $selection, $interval, $start, $end, $option_attributes));
    }
    if(      $meridiem_requirement ===  1 and ! isset($options['meridiem']) ){
      trigger_error('Imp_time using 12 hour format without meridiem option.', E_USER_ERROR);
    }elseif( $meridiem_requirement === -1 and $options['meridiem'] !== true  ){
      trigger_error('Imp_time using 24 hour format and meridiem option.', E_USER_ERROR);
    }
    return $html;
  }



  // main smarty tag handler

  function imp_tag( $user_parameters, $content, &$smarty, $repeat, $tag_type){

    $template_variables = &$smarty->get_template_vars();

    if(      $repeat === null  ){
      $block_type = 'neither'   ;
    }elseif( $repeat === true  ){
      $block_type = 'open' ;
    }elseif( $repeat === false ){
      $block_type = 'close';
    }



    // constructing tag definition

    $tag = array();
    $tag['type']                = $tag_type;
    $tag['repeat']              = $repeat;
    $tag['internal_parameters'] = array();
    $tag['parent']              = &$template_variables['imp_tags']['containing_child'];



    // inserting tag into stack, tree, lists, and other structures

    $template_variables['imp_tags']['stack'][] = &$tag;
    if( $block_type !== 'close' ){
      if( $tag['parent'] === null ){
        $template_variables['imp_tags']['tree'][] = &$tag;
      }else{
        $tag['parent']['children'][] = &$tag;
      }
    }else{
      if( $tag['parent'] === null ){ trigger_error('You have not opened the '.$tag_type.' tag.', E_USER_ERROR); }
      if( $tag['parent']['type'] != $tag_type ){ trigger_error('The closing tag "'.$tag_type.'" was mismatched with the opening tag "'.$tag['parent']['type'].'".', E_USER_ERROR); }
      $tag['parent']['close'] = &$tag;
      $tag = &$tag['parent'];
    }



    // finding appropriate imp

    if( isset($user_parameters['imp']) ){
      if( $tag['parent'] !== null and $tag['parent']['imp_smarty_handle'] !== $user_parameters['imp'] ){ trigger_error('An imp tag explicitly associated with the imp "'.$user_parameters['imp'].'" was found in the context of tags associated with the imp "'.$tag['parent']['imp_smarty_handle'].'".', E_USER_ERROR); }
      $tag['imp_smarty_handle'] = $user_parameters['imp'];
    }elseif( $tag['parent'] ){
      $tag['imp_smarty_handle'] = $tag['parent']['imp_smarty_handle'];
    }else{
      if( ! isset($template_variables['imp']) ){ trigger_error('The tag '.$tag_type.' is not associated with an Imp, and there is no unambiguous choice for a default Imp.', E_USER_ERROR); }
      $tag['imp_smarty_handle'] = IMP_DEFAULT_SMARTY_HANDLE;
    }
    if( ! isset($template_variables['imps'][$tag['imp_smarty_handle']]) ){ trigger_error($tag_type.' is associated with a non-existant Imp "'.$tag['imp_smarty_handle'].'"', E_USER_ERROR); }
    $imp = &$template_variables['imps'][$tag['imp_smarty_handle']];
    if(
      $imp->settings['require_imp_form_tags'] and
      $tag_type !== 'imp_form' and
      ! Imp::ancestor($smarty, 'imp_form')
    ){
      trigger_error('The "'.$tag_type.'" tag is not contained within "imp_form" tags.', E_USER_ERROR);
    }







    // compiling parameter sources
    $tag['parameters'] = array();
    foreach( $template_variables['imp_functions'][$tag_type]['parameters'] as $which_parameter => $each_parameter ){
      if( $each_parameter['allowance'] === 'required'   and ! isset($user_parameters[$which_parameter]) ){ trigger_error(ucwords($tag_type).' requires the "'.$which_parameter.'" parameter.', E_USER_ERROR); }
      if( $each_parameter['allowance'] === 'prohibited' and   isset($user_parameters[$which_parameter]) ){ trigger_error(ucwords($tag_type). ' forbids the "'.$which_parameter.'" parameter.', E_USER_ERROR); }
      if( isset($each_parameter['default']) ){
        $tag['parameters'][$which_parameter] = $each_parameter['default'];
      }
    }
    foreach( $tag['internal_parameters'] as $which_internal_parameter => $each_internal_parameter ){
      if( isset($user_parameters[$which_internal_parameter]) ){ trigger_error('The user specification for parameter "'.$which_internal_parameter.'" in '.$tag_type.' conflicts with an internally-generated setting.', E_USER_ERROR); }
      $tag['parameters'][$which_internal_parameter] = $each_internal_parameter;
    }
    foreach( $user_parameters as $which_user_parameter => $each_user_parameter ){
      $tag['parameters'][$which_user_parameter] = $each_user_parameter;
    }

    // creating attributes array

    $attributes = array();
    foreach( $tag['parameters'] as $which_parameter => $each_parameter ){
      $parameter_definitions = $template_variables['imp_functions'][$tag_type]['parameters'];
      $has_parameter_definition = isset($parameter_definitions[$which_parameter]);
      $allows_parameter_attribution = (isset($parameter_definitions[$which_parameter]['attribute']) and $parameter_definitions[$which_parameter]['attribute']);
      if( (!$has_parameter_definition) or $allows_parameter_attribution ){
        $attributes[$which_parameter] = $each_parameter;
      }

    }
    $tag['attributes'] = $attributes;



    // all tag  processing


    if( isset($tag['parameters']['preset']) ){
      $imp->preset($tag['parameters']['name'], $tag['parameters']['preset']);
    }
    if( isset($tag['parameters'][ 'reset']) ){
      $imp->reset( $tag['parameters']['name'], $tag['parameters'][ 'reset']);
    }



    // tag-specific processing

    $html = '';

    if( $tag_type === 'imp_error' ){
      if( $block_type === 'close' ){
        $imp->validation_handler->tag($tag['parameters']['requirement']);
        $report_error = false;
	if( isset($imp->validation_handler->requirements[$tag['parameters']['requirement']]) ){
          $report_error ^= $imp->validation_handler->validate($tag['parameters']['requirement']) === false;
	}else{
          if( $imp->settings['require_error_requirements'] ){ trigger_error('The imp_error for the requirement "'.$tag['parameters']['requirement'].'" has no associated (in)validators.', E_USER_ERROR); }
	}
        if( $report_error ){
          $html .= $content;
        }
      }


    }elseif( $tag_type === 'imp_form' ){
      if( $block_type === 'open' ){
        $imp->hide('imp', $imp->instance());
        $imp->note('smarty_handle'  , $imp->smarty_handle);
        $imp->note('status', 'rendered');
      }else{
        if( $imp->settings['require_imp_error_tags'] ){
          if( ! $imp->settings['require_imp_form_tags'] ){ trigger_error('You cannot require error tags without requiring form tags.', E_USER_ERROR); }
	  $untagged = $imp->validation_handler->untagged();
          if( $untagged ){
            foreach( $untagged as $which_requirement => $each_requirement ){
              $untagged_requirement_names[] = $which_requirement;
            }
            trigger_error('There are no imp_error tags to indicate errors for the following requirement(s): "'.implode('", "', $untagged_requirement_names).'".', E_USER_WARNING);
          }
        }
        $hide_strings = '';
        foreach( $imp->hides as $which_hide => $each_hide ){
          $hides_string .= "\r".'  '.$imp->html_generic_input('hidden', $which_hide, $imp->display_value($which_hide));
        }
        if( strlen($hides_string) ){
          $hides_string = "\r".'<div>'.$hides_string."\r".'</div>'."\r";
          $content      = "\r".        $content              ."\r";
        }
	$content      = $hides_string.$content;
        $html .= $imp->html_generic_tag('form', $attributes, 'both', $content);
      }


    }elseif( $tag_type === 'imp_hidden' ){
      $html .= $imp->html_generic_input('hidden', $tag['parameters']['name'], $imp->display_value($tag['parameters']['name']), $attributes);


    }elseif( $tag_type === 'imp_text' ){
      $html .= $imp->html_generic_input('text', $tag['parameters']['name'], $imp->display_value($tag['parameters']['name']), $attributes);
    }elseif( $tag_type === 'imp_textarea' ){
      $html .= $imp->html_generic_tag('textarea', $attributes, 'both', $imp->display_value($tag['parameters']['name']));
    }elseif( $tag_type === 'imp_password' ){
      $display_value = $imp->display_value($tag['parameters']['name']);
      if( $imp->settings['behavior_passwords_clear_by_default'] and $imp->fields[$tag['parameters']['name']]['reset'] === null ){
        $display_value = '';
      }
      $html .= $imp->html_generic_input('password', $tag['parameters']['name'], $display_value, $attributes);


    }elseif( $tag_type === 'imp_checkbox' ){
      if( $imp->display_value($tag['parameters']['name']) /* === $attributes['value'] */ ){
        $attributes = array_merge(array('checked' => "checked"), $attributes);
      }
      $attributes['value'] = 'on';
      $html .= $imp->html_generic_input('checkbox', $tag['parameters']['name'], $imp->display_value($tag['parameters']['name']), $attributes);
    }elseif( $tag_type === 'imp_radio' ){
      if( $imp->display_value($tag['parameters']['name']) === $tag['parameters']['value'] ){
        $attributes = array_merge(array('checked' => "checked"), $attributes);
      }
      $html .= $imp->html_generic_input('radio', $tag['parameters']['name'], $imp->display_value($tag['parameters']['name']), $attributes);


    }elseif( $tag_type === 'imp_select' ){
      if( $block_type === 'close' ){
        $html .= $imp->html_generic_tag('select', $attributes, 'both', $content);
      }
    }elseif( $tag_type === 'imp_option' ){
      $parent_select_tag = &Imp::ancestor($smarty, 'imp_select');
      if( ! $parent_select_tag ){ trigger_error('An imp_option was called outside of imp_select tags.', E_USER_ERROR); }
      if( $block_type === 'close' ){
        $html .= $imp->html_option($tag['parameters']['value'], $content, $attributes);
      }
    }elseif( $tag_type === 'imp_dropdown' ){
      $options_string = '';
      if( $tag['parameters']['grouped'] ){
        foreach( $tag['parameters']['options'] as $which_group => $each_group ){
          $options_string .= $imp->html_generic_tag('optgroup', array('label', $which_group))."\r";
          $options_string .= $imp->html_options( $each_group, $imp->display_value($tag['parameters']['name']) );
        }
      }else{
        $options_string .= $imp->html_options( $tag['parameters']['options'], $imp->display_value($tag['parameters']['name']) );
      }
      $html .= $this->html_generic_tag('select', $attributes, 'both', $options_string);


    }elseif( $tag_type === 'imp_file' ){
      $parent_form_tag = &Imp::ancestor($smarty, 'imp_form');
      if( $imp->settings['enforce_imp_file_requirements'] ){
        if( ! $parent_form_tag ){ trigger_error('The imp_file tag must be within imp_form tags, for imp_file requirement enforcing to work proerly.', E_USER_ERROR); }
        if( ! isset($parent_form_tag['attributes']['enctype']) or strtolower($parent_form_tag['attributes']['enctype']) !== 'multipart/form-data' ){ trigger_error('The the enctype of the form must be "multipart/form-data" for imp_file to function properly.', E_USER_ERROR ); }
        if( ! isset($parent_form_tag['attributes']['method' ]) or strtolower($parent_form_tag['attributes']['method' ]) !== 'post' ){ trigger_error('The the from submission method must be "post" for imp_file to function properly.', E_USER_ERROR ); }
      }
      $imp->note_cloak_type($tag['parameters']['name'], $tag_type);
      $file_selection_string = $this->html_generic_input('file', $tag['parameters']['name'], $imp->display_value($tag['parameters']['name']), $attributes);
      if( $imp->submitted() ){
        $file_info = $imp->submission($tag['parameters']['name']);
	/*
        if( $imp->settings['optimize_uploads'] ){
	  $_SESSION['tmp_name'] = $file_info['tmp_name'];
	  $imp->preserve_upload($file_info['tmp_name'], $tag['parameters']['name']);
          $file_selection_string = 'ALREADY UPLOADED: "'.$file_info['name'].'" (REPLACE: '.$file_selection_string.')';
        }
	*/
      }
      $html .= $file_selection_string;


    }elseif( $tag_type === 'imp_image' ){
      trigger_error('The '.$tag_type.' tag function has not yet been written!', E_USER_ERROR);

    }elseif( $tag_type === 'imp_submit' ){
      $html .= $this->html_generic_input('submit', $tag['parameters']['name'], $tag['parameters']['label'], $attributes);


    }elseif( $tag_type === 'imp_datetime' or $tag_type === 'imp_date' or $tag_type === 'imp_time' ){
      $imp->note_cloak_type($tag['parameters']['name'], $tag_type);
      $date = null;
      $time = null;
      if(      $tag_type === 'imp_datetime' ){
        $datetime = $imp->display_value($tag['parameters']['name']) === null ? date('Y-m-d H:i:s') : $imp->display_value($tag['parameters']['name']);
        $date_and_time = explode(' ', $datetime   );
        $date = $date_and_time[0];
        $time = $date_and_time[1];
      }elseif( $tag_type === 'imp_date'     ){
        $date     = $imp->display_value($tag['parameters']['name']) === null ? date('Y-m-d'      ) : $imp->display_value($tag['parameters']['name']);
        $date_and_time = explode(' ', $date);
        if( sizeof($date_and_time) === 2 ){
          $date = $date_and_time[0];
        }
      }elseif( $tag_type === 'imp_time'     ){
        $time     = $imp->display_value($tag['parameters']['name']) === null ? date(      'H:i:s') : $imp->display_value($tag['parameters']['name']);
        $date_and_time = explode(' ', $datetime   );
        if( sizeof($date_and_time) === 2 ){
          $time = $date_and_time[1];
        }
      }
      $html .= $imp->html_datetime_inputs($tag['parameters']['name'], $date, $time, $tag['parameters']['format'], $tag['parameters']);

    }


    if(      $block_type === 'open'  ){ // if this is an opening block tag
      $template_variables['imp_tags']['containing_child'] = &$tag;
    }elseif( $block_type === 'close' ){
      $template_variables['imp_tags']['containing_child'] = &$tag['parent'];
    }

    return $html;
  }

  //
  //
  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////








  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////
  //
  // PRESERVED FOR BACKWARDS COMPATIBILITY

  function is_submission(){
    return $this->submitted();
  }
  function received(){
    return $this->submitted();
  }
  function is_valid_submission(){
    return $this->validate();
  }

  function condition_function(){
    $arguments = func_get_args;
    $this->invalidator(array(array_shift($arguments), array_shift($arguments), $arguments));
  }

  function tipoff_function(){
    $this->tipoff(array_shift($arguments), func_get_args());
  }

  function default_value( $field_name, $value ){
    $this->preset($field_name, $value);
  }
  function default_function(){
    $arguments = func_get_args;
    $this->preset(array_shift($arguments), array_shift($arguments), $arguments);
  }
  function refault_value( $field_name, $value ){
    $this->reset($field_name, $value);
  }
  function refault_function(){
    $arguments = func_get_args;
    $this->reset(array_shift($arguments), array_shift($arguments), $arguments);
  }

  //
  //
  //////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////

}
















/*******************************************************************************
********************************************************************************
********************************************************************************
********************************************************************************
** Foot
********************************************************************************
********************************************************************************
********************************************************************************
*******************************************************************************/




} // end of Imp definition ?>
